﻿using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using md.imi.membrane.common.Interfaces;
using md.imi.membrane.constants;
using md.imi.membranes.objects.alphabet;
using md.imi.membranes.objects.membranes;

// Q1: multiplicity of alphabet objects in strings <a, b^2, cc>
// Q2: multiplicity of strings in multiset : Answer : the user can present by 2 modes (with multiplicity and
// with expanded strings), but the system will compute and present strings with multiplicity only.

// the system may present the strings with multiplicity,
//  or expanded strings.
// Q3:
namespace md.imi.membranes.objects.psystems
{
    [Serializable]
    public class PSystem : ICacheableObject, IDisposable, IValidator
    {
        #region Events

        public delegate void AddNewLogMesageDelegate(LogEntry logEntry);
        public event AddNewLogMesageDelegate AddNewLogMesageEvent;

        public void LogInfo(string format, params  object[] parameteres)
        {
            if (AddNewLogMesageEvent != null)
            {
                AddNewLogMesageEvent(new LogEntry()
                                         {
                                             Message = String.Format(format, parameteres),
                                             MessageType = LogEntry.MsgType.Info,
                                             Sender = this,
                                             TimeStamp = DateTime.Now
                                         });
            }
        }

        public void LogWarn(string format, params  object[] parameteres)
        {
            if (AddNewLogMesageEvent != null)
            {
                AddNewLogMesageEvent(new LogEntry()
                {
                    Message = String.Format(format, parameteres),
                    MessageType = LogEntry.MsgType.Warning,
                    Sender = this,
                    TimeStamp = DateTime.Now
                });
            }
        }

        public void LogError(string format, params  object[] parameteres)
        {
            if (AddNewLogMesageEvent != null)
            {
                AddNewLogMesageEvent(new LogEntry()
                {
                    Message = String.Format(format, parameteres),
                    MessageType = LogEntry.MsgType.Warning,
                    Sender = this,
                    TimeStamp = DateTime.Now
                });
            }
        }

        #endregion Events

        #region Public properties



        /// <summary>
        ///     The unical ID of the objects which identifies it
        /// </summary>
        //[XmlAttributeAttribute(DataType = "string")]
        public Guid Id { get; set; }

        /// <summary>
        ///    This flag indicates if this pSystem is evaluated on the
        /// next system evaluation, or just kept for history
        /// </summary>
        [XmlAttributeAttribute(DataType = "boolean")]
        public bool Active { get; set; }

        /// <summary>
        ///     Identifies system type (PSystem type)
        /// </summary>
        public PSystemType SystemType { get; set; }

        /// <summary>
        ///     User string (short system description)
        /// </summary>
        public String Description { get; set; }

        /// <summary>
        ///     The version of software used to manage this PSystem
        /// </summary>
        public int Version { get; set; }

        /// <summary>
        ///     The membranes hierarchy.
        /// Regarding serialization, the tree is serialized trough MembranesList property
        /// </summary>
        public MembranesTree Membranes { get; set; }

        /// <summary>
        ///     All alphabet objects used in this PSystem
        /// </summary>
        [XmlArray("AlphabetObjects"), XmlArrayItem("AlphabetObject", typeof(AlphabetObject))]
        public List<AlphabetObject> AlphabetObjects { get; set; }

        /// <summary>
        ///     Possible solutions list
        /// </summary>
       [XmlArray("Solutions"), XmlArrayItem("List<Solution>", typeof(List<Solution>))]
        public List<List<Solution>> Solutions = new List<List<Solution>>();

        /// <summary>
        ///     Concrete solution used to evaluate this PSystem configuration
        /// </summary>
       //[XmlArray("Solution"), XmlArrayItem("Solution", typeof(Solution))]
        public List<Solution> SourceSolution = null;//new Solution();
        
        /// <summary>
        ///     The unical ID of the source solution which was evaluated to this
        /// configuration.
        /// </summary>
        //[XmlAttributeAttribute(DataType = "string")]
        //public Guid SourceSolutionId { get; set; }
        
        #endregion Public properties

        /// <summary>
        ///
        /// </summary>
        public PSystem()
        {
            this.Active = true;
            this.Id = Guid.NewGuid();
            this.SystemType = PSystemType.StringsWithReplicationRules;
            this.Description = "Strings with replication rules default configuration";
            this.Version = 1;
            this.Membranes = new MembranesTree { Label = "Environment", Configuration = (this as PSystemsTree) };

            this.AlphabetObjects = new List<AlphabetObject>();
        }


        /// <summary>
        ///     Copy constructor
        /// TODO: after a new copy is created, update field  SourceSolution
        /// </summary>
        /// <param name="parrentConfig"></param>
        protected PSystem(PSystemsTree parrentConfig)
        {

            Id          = parrentConfig.Id;
            Active      = parrentConfig.Active;
            SystemType  = parrentConfig.SystemType;
            Description = parrentConfig.Description;
            Version     = parrentConfig.Version;

            Membranes = new MembranesTree( parrentConfig.Membranes);
            AlphabetObjects = new List<AlphabetObject>();
            foreach (var ao in parrentConfig.AlphabetObjects)
            {
                AlphabetObjects.Add(new AlphabetObject(ao));
            }
        }

        #region Implementation of ICacheableObject

        /// <summary>
        ///     The method must be capable to dump (save) the own object into a XML file,
        /// whose name is automatically calculated as a {GUID}.XML.
        /// </summary>
        /// <returns></returns>
        public bool PersistObjectToCach()
        {
            var fileName = String.Format("{0}.xml", Id.ToString());

            // serialize membranes tree,
            // serialize alphabet objects
            Membranes.Dispose();
            Membranes = null;

            foreach (var alphabetObject in AlphabetObjects)
                alphabetObject.Dispose();

            AlphabetObjects.Clear();
            AlphabetObjects = null;

            return true;
        }

        /// <summary>
        ///     On request, function composes the file name
        /// {GUID}.XML. If the file is found, the persisted objects are loaded back to
        /// memory, and the XML file is removed.
        ///
        /// This function is usually called by the property getter
        /// </summary>
        /// <returns></returns>
        public bool RestoreObjectFromCach()
        {
            throw new NotImplementedException();
        }

        #endregion Implementation of ICacheableObject

        /// <summary>
        ///     Free up all resources
        /// </summary>
        public void Dispose()
        {
            if (Membranes != null) Membranes.Dispose();
            if (AlphabetObjects != null) AlphabetObjects.Clear();
        }

        /// <summary>
        ///     Interate trough all PSystems and calculate paths of leaf configurations
        ///  - if a PSystem has null or zero
        /// </summary>
        /// <returns></returns>
        public bool CalculateNextPath()
        {
            Solutions.Clear();
            LogInfo("Prepare calculations");
            Console.WriteLine("-------------------[ Calculate solutions]----------------");
            Solutions = CalculateMembraneSolutions(Membranes.Membranes, Solutions);

            return (Solutions.Count > 0);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="membranes"></param>
        /// <param name="solutions"></param>
        /// <returns></returns>
        private List<List<Solution>> CalculateMembraneSolutions(List<MembranesTree> membranes, List<List<Solution>> solutions)
        {
            foreach (var membrane in membranes)
            {
                Console.WriteLine("Calculate for " + membrane.ToString());
                solutions = membrane.CalculateSolutions(solutions);

                if (membrane.Membranes != null && membrane.Membranes.Count > 0)
                {
                    solutions = CalculateMembraneSolutions(membrane.Membranes, solutions);
                }
            }

            return solutions;
        }

        /// <summary>
        ///     Performs PSystem validation
        /// </summary>
        /// <returns></returns>
        public bool IsValid()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///   Apply all calculated solutions
        /// </summary>
        /// <returns></returns>
        public bool ApplyAllSolutions(PSystemsTree parrentConfig)
        {
            foreach (var solutionsList in Solutions)
            {
                // do not apply twice the same solutions set
                // action was twice requested
                if (IsSolutionApplied(solutionsList, parrentConfig))
                {
                    continue;
                }

                var ps = new PSystemsTree(parrentConfig);
                ps.Id = Guid.NewGuid();

                if (ApplySolutions(solutionsList, ps) == false)
                {
                    Console.WriteLine("Cannot evaluate selected solution, already did");
                    continue;   // cannot evaluate next solutions
                }


                ps.SourceSolution = solutionsList;
                if (parrentConfig.PSystems == null)
                {
                    parrentConfig.PSystems = new List<PSystemsTree>();
                }

                parrentConfig.PSystems.Add(ps);
                parrentConfig.OnChange();
            }

            return true;
        }

        private bool IsSolutionApplied(List<Solution> solutionsList, PSystemsTree parrentConfig)
        {
            if ((solutionsList == null) || (parrentConfig == null) || 
                (parrentConfig.PSystems == null))
            {
                return false;
            }

            foreach (var cfg in parrentConfig.PSystems)
            {
                if (solutionsList == cfg.SourceSolution)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        ///     Apply selected solutions only. Each applyed solution generates a new 
        /// configuration which is added to parent configuration of the solution
        /// being processed
        /// </summary>
        /// <param name="solutions"></param>
        /// <returns></returns>
        private bool ApplySolutions(List<Solution> solutions, PSystemsTree config)
        {
            foreach (var solution in solutions)
            {
                solution.AppliedRule.ApplyRule(solution, config);
            }

            return true;
        }
    }
}